﻿namespace Stopwatch.Core
{
    using System;
    using System.Threading;

    public class StopWatch : IDisposable
    {
        public DateTime PauseTime { get; private set; }
        public DateTime StartTime { get; private set; }
        public DateTime NewTime { get; private set; }

        const int PERIOD = 10;

        Timer timer;

        public Time Main { get; private set; }

        public Time Sub { get; private set; }

        public bool SubEnabled { get; private set; }

        public Status Status { get; private set; }

        public StopWatch(WatchState state = null)
        {
            Main = Time.Zero;
            Sub = Time.Zero;

            if (state == null)
            {
                timer = new Timer(TimerCallback, null, Timeout.Infinite, Timeout.Infinite);
            }
            else
            {
                switch (state.Status)
                {
                    case Status.Started:
                        StartTime = state.StartTime;
                        NewTime = state.NewTime;
                        timer = new Timer(TimerCallback, null, PERIOD, PERIOD);
                        break;
                    case Status.Paused:
                        StartTime = state.StartTime + (DateTime.Now - state.PauseTime);
                        NewTime = state.NewTime + (DateTime.Now - state.PauseTime);
                        PauseTime = DateTime.Now;
                        timer = new Timer(TimerCallback, null, Timeout.Infinite, Timeout.Infinite);
                        // update Main & Sub.
                        TimerCallback(null);
                        break;
                    default:
                        break;
                }

                Status = state.Status;
                SubEnabled = state.SubEnabled;
            }
        }

        public void Start()
        {
            if (Status == Status.Ready)
            {
                StartTime = DateTime.Now;
                NewTime = DateTime.Now;
            }
            else if (Status == Status.Paused)
            {
                StartTime += (DateTime.Now - PauseTime);
                NewTime += (DateTime.Now - PauseTime);
            }

            if (Status != Status.Stoped || Status != Status.Started)
            {
                timer.Change(PERIOD, PERIOD);
                Status = Status.Started;
            }
        }

        public void Stop()
        {
            timer.Change(Timeout.Infinite, Timeout.Infinite);
            Status = Status.Stoped;
            Main = Time.Zero;
            Sub = Time.Zero;
            SubEnabled = false;

            if (Stoped != null)
            {
                Stoped(this, EventArgs.Empty);
            }

            Status = Status.Ready;
        }

        public void Pause()
        {
            timer.Change(Timeout.Infinite, Timeout.Infinite);
            Status = Status.Paused;
            PauseTime = DateTime.Now;

            if (Paused != null)
            {
                Paused(this, EventArgs.Empty);
            }
        }

        public Time AddStopTime()
        {
            SubEnabled = true;

            var sub = Sub;
            NewTime = DateTime.Now;
            Sub = Time.Zero;

            return sub;
        }

        private void TimerCallback(object state)
        {
            var now = DateTime.Now;
            var main = now - StartTime;
            var sub = now - NewTime;

            Main = new Time((int)main.TotalHours, main.Minutes, main.Seconds, main.Milliseconds / 10);
            Sub = new Time((int)sub.TotalHours, sub.Minutes, sub.Seconds, sub.Milliseconds / 10);

            if (Tick != null)
            {
                Tick(this, EventArgs.Empty);
            }
        }

        public event EventHandler Paused;
        public event EventHandler Stoped;
        public event EventHandler Tick;

        public void Dispose()
        {
            if (timer != null)
            {
                timer.Dispose();
                timer = null;
            }
        }
    }
}
